
{*******************************************************}
{                                                       }
{       Turbo Pascal Version 7.0                        }
{       Turbo Vision Unit                               }
{                                                       }
{       Copyright (c) 1992 Borland International        }
{                                                       }
{*******************************************************}

unit Editors;

{$I-,O+,F+,V-,X+,S-}

interface

uses Drivers, Objects, Views, Dialogs;

const
  cmFind        = 82;
  cmReplace     = 83;
  cmSearchAgain = 84;

const
  cmCharLeft    = 500;
  cmCharRight   = 501;
  cmWordLeft    = 502;
  cmWordRight   = 503;
  cmLineStart   = 504;
  cmLineEnd     = 505;
  cmLineUp      = 506;
  cmLineDown    = 507;
  cmPageUp      = 508;
  cmPageDown    = 509;
  cmTextStart   = 510;
  cmTextEnd     = 511;
  cmNewLine     = 512;
  cmBackSpace   = 513;
  cmDelChar     = 514;
  cmDelWord     = 515;
  cmDelStart    = 516;
  cmDelEnd      = 517;
  cmDelLine     = 518;
  cmInsMode     = 519;
  cmStartSelect = 520;
  cmHideSelect  = 521;
  cmIndentMode  = 522;
  cmUpdateTitle = 523;

const
  edOutOfMemory   = 0;
  edReadError     = 1;
  edWriteError    = 2;
  edCreateError   = 3;
  edSaveModify    = 4;
  edSaveUntitled  = 5;
  edSaveAs        = 6;
  edFind          = 7;
  edSearchFailed  = 8;
  edReplace       = 9;
  edReplacePrompt = 10;

const
  efCaseSensitive   = $0001;
  efWholeWordsOnly  = $0002;
  efPromptOnReplace = $0004;
  efReplaceAll      = $0008;
  efDoReplace       = $0010;
  efBackupFiles     = $0100;

const
  CIndicator = #2#3;
  CEditor    = #6#7;
  CMemo      = #26#27;

const
  MaxLineLength = 256;

type
  TEditorDialog = function(Dialog: Integer; Info: Pointer): Word;

type
  PIndicator = ^TIndicator;
  TIndicator = object(TView)
    Location: TPoint;
    Modified: Boolean;
    constructor Init(var Bounds: TRect);
    procedure Draw; virtual;
    function GetPalette: PPalette; virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
    procedure SetValue(ALocation: TPoint; AModified: Boolean);
  end;

type
  PEditBuffer = ^TEditBuffer;
  TEditBuffer = array[0..65519] of Char;

type
  PEditor = ^TEditor;
  TEditor = object(TView)
    HScrollBar: PScrollBar;
    VScrollBar: PScrollBar;
    Indicator: PIndicator;
    Buffer: PEditBuffer;
    BufSize: Word;
    BufLen: Word;
    GapLen: Word;
    SelStart: Word;
    SelEnd: Word;
    CurPtr: Word;
    CurPos: TPoint;
    Delta: TPoint;
    Limit: TPoint;
    DrawLine: Integer;
    DrawPtr: Word;
    DelCount: Word;
    InsCount: Word;
    IsValid: Boolean;
    CanUndo: Boolean;
    Modified: Boolean;
    Selecting: Boolean;
    Overwrite: Boolean;
    AutoIndent: Boolean;
    constructor Init(var Bounds: TRect;
      AHScrollBar, AVScrollBar: PScrollBar;
      AIndicator: PIndicator; ABufSize: Word);
    constructor Load(var S: TStream);
    destructor Done; virtual;
    function BufChar(P: Word): Char;
    function BufPtr(P: Word): Word;
    procedure ChangeBounds(var Bounds: TRect); virtual;
    procedure ConvertEvent(var Event: TEvent); virtual;
    function CursorVisible: Boolean;
    procedure DeleteSelect;
    procedure DoneBuffer; virtual;
    procedure Draw; virtual;
    function GetPalette: PPalette; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure InitBuffer; virtual;
    function InsertBuffer(var P: PEditBuffer; Offset, Length: Word;
      AllowUndo, SelectText: Boolean): Boolean;
    function InsertFrom(Editor: PEditor): Boolean; virtual;
    function InsertText(Text: Pointer; Length: Word;
      SelectText: Boolean): Boolean;
    procedure ScrollTo(X, Y: Integer);
    function Search(const FindStr: String; Opts: Word): Boolean;
    function SetBufSize(NewSize: Word): Boolean; virtual;
    procedure SetCmdState(Command: Word; Enable: Boolean);
    procedure SetSelect(NewStart, NewEnd: Word; CurStart: Boolean);
    procedure SetState(AState: Word; Enable: Boolean); virtual;
    procedure Store(var S: TStream);
    procedure TrackCursor(Center: Boolean);
    procedure Undo;
    procedure UpdateCommands; virtual;
    function Valid(Command: Word): Boolean; virtual;
  private
    LockCount: Byte;
    UpdateFlags: Byte;
    KeyState: Integer;
    function CharPos(P, Target: Word): Integer;
    function CharPtr(P: Word; Target: Integer): Word;
    function ClipCopy: Boolean;
    procedure ClipCut;
    procedure ClipPaste;
    procedure DeleteRange(StartPtr, EndPtr: Word; DelSelect: Boolean);
    procedure DoUpdate;
    procedure DoSearchReplace;
    procedure DrawLines(Y, Count: Integer; LinePtr: Word);
    procedure FormatLine(var DrawBuf; LinePtr: Word;
      Width: Integer; Colors: Word);
    procedure Find;
    function GetMousePtr(Mouse: TPoint): Word;
    function HasSelection: Boolean;
    procedure HideSelect;
    function IsClipboard: Boolean;
    function LineEnd(P: Word): Word;
    function LineMove(P: Word; Count: Integer): Word;
    function LineStart(P: Word): Word;
    procedure Lock;
    procedure NewLine;
    function NextChar(P: Word): Word;
    function NextLine(P: Word): Word;
    function NextWord(P: Word): Word;
    function PrevChar(P: Word): Word;
    function PrevLine(P: Word): Word;
    function PrevWord(P: Word): Word;
    procedure Replace;
    procedure SetBufLen(Length: Word);
    procedure SetCurPtr(P: Word; SelectMode: Byte);
    procedure StartSelect;
    procedure ToggleInsMode;
    procedure Unlock;
    procedure Update(AFlags: Byte);
  end;

type
  TMemoData = record
    Length: Word;
    Buffer: TEditBuffer;
  end;

type
  PMemo = ^TMemo;
  TMemo = object(TEditor)
    constructor Load(var S: TStream);
    function DataSize: Word; virtual;
    procedure GetData(var Rec); virtual;
    function GetPalette: PPalette; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure SetData(var Rec); virtual;
    procedure Store(var S: TStream);
  end;

type
  PFileEditor = ^TFileEditor;
  TFileEditor = object(TEditor)
    FileName: FNameStr;
    constructor Init(var Bounds: TRect;
      AHScrollBar, AVScrollBar: PScrollBar;
      AIndicator: PIndicator; AFileName: FNameStr);
    constructor Load(var S: TStream);
    procedure DoneBuffer; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure InitBuffer; virtual;
    function LoadFile: Boolean;
    function Save: Boolean;
    function SaveAs: Boolean;
    function SaveFile: Boolean;
    function SetBufSize(NewSize: Word): Boolean; virtual;
    procedure Store(var S: TStream);
    procedure UpdateCommands; virtual;
    function Valid(Command: Word): Boolean; virtual;
  end;

type
  PEditWindow = ^TEditWindow;
  TEditWindow = object(TWindow)
    Editor: PFileEditor;
    constructor Init(var Bounds: TRect;
      FileName: FNameStr; ANumber: Integer);
    constructor Load(var S: TStream);
    procedure Close; virtual;
    function GetTitle(MaxSize: Integer): TTitleStr; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure SizeLimits(var Min, Max: TPoint); virtual;
    procedure Store(var S: TStream);
  end;

function DefEditorDialog(Dialog: Integer; Info: Pointer): Word;
function CreateFindDialog: PDialog;
function CreateReplaceDialog: PDialog;
function StdEditorDialog(Dialog: Integer; Info: Pointer): Word;

const
  WordChars: set of Char = ['0'..'9', 'A'..'Z', '_', 'a'..'z'];
  EditorDialog: TEditorDialog = DefEditorDialog;
  EditorFlags: Word = efBackupFiles + efPromptOnReplace;
  FindStr: String[80] = '';
  ReplaceStr: String[80] = '';
  Clipboard: PEditor = nil;

type
  TFindDialogRec = record
    Find: String[80];
    Options: Word;
  end;

type
  TReplaceDialogRec = record
    Find: String[80];
    Replace: String[80];
    Options: Word;
  end;

const
  REditor: TStreamRec = (
    ObjType: 70;
    VmtLink: Ofs(TypeOf(TEditor)^);
    Load: @TEditor.Load;
    Store: @TEditor.Store
  );
  RMemo: TStreamRec = (
    ObjType: 71;
    VmtLink: Ofs(TypeOf(TMemo)^);
    Load: @TMemo.Load;
    Store: @TMemo.Store
  );
  RFileEditor: TStreamRec = (
    ObjType: 72;
    VmtLink: Ofs(TypeOf(TFileEditor)^);
    Load: @TFileEditor.Load;
    Store: @TFileEditor.Store
  );
  RIndicator: TStreamRec = (
    ObjType: 73;
    VmtLink: Ofs(TypeOf(TIndicator)^);
    Load: @TIndicator.Load;
    Store: @TIndicator.Store
  );
  REditWindow: TStreamRec = (
    ObjType: 74;
    VmtLink: Ofs(TypeOf(TEditWindow)^);
    Load: @TEditWindow.Load;
    Store: @TEditWindow.Store
  );

procedure RegisterEditors;

implementation

uses Memory, Dos, App, StdDlg, MsgBox;

const
  ufUpdate = $01;
  ufLine   = $02;
  ufView   = $04;

const
  smExtend = $01;
  smDouble = $02;

const
  sfSearchFailed = $FFFF;

const
  FirstKeys: array[0..37 * 2] of Word = (37,
    Ord(^A), cmWordLeft, Ord(^C), cmPageDown,
    Ord(^D), cmCharRight, Ord(^E), cmLineUp,
    Ord(^F), cmWordRight, Ord(^G), cmDelChar,
    Ord(^H), cmBackSpace, Ord(^K), $FF02,
    Ord(^L), cmSearchAgain, Ord(^M), cmNewLine,
    Ord(^O), cmIndentMode, Ord(^Q), $FF01,
    Ord(^R), cmPageUp, Ord(^S), cmCharLeft,
    Ord(^T), cmDelWord, Ord(^U), cmUndo,
    Ord(^V), cmInsMode, Ord(^X), cmLineDown,
    Ord(^Y), cmDelLine, kbLeft, cmCharLeft,
    kbRight, cmCharRight, kbCtrlLeft, cmWordLeft,
    kbCtrlRight, cmWordRight, kbHome, cmLineStart,
    kbEnd, cmLineEnd, kbUp, cmLineUp,
    kbDown, cmLineDown, kbPgUp, cmPageUp,
    kbPgDn, cmPageDown, kbCtrlPgUp, cmTextStart,
    kbCtrlPgDn, cmTextEnd, kbIns, cmInsMode,
    kbDel, cmDelChar, kbShiftIns, cmPaste,
    kbShiftDel, cmCut, kbCtrlIns, cmCopy,
    kbCtrlDel, cmClear);
  QuickKeys: array[0..8 * 2] of Word = (8,
    Ord('A'), cmReplace, Ord('C'), cmTextEnd,
    Ord('D'), cmLineEnd, Ord('F'), cmFind,
    Ord('H'), cmDelStart, Ord('R'), cmTextStart,
    Ord('S'), cmLineStart, Ord('Y'), cmDelEnd);
  BlockKeys: array[0..5 * 2] of Word = (5,
    Ord('B'), cmStartSelect, Ord('C'), cmPaste,
    Ord('H'), cmHideSelect, Ord('K'), cmCopy,
    Ord('Y'), cmCut);
  KeyMap: array[0..2] of Pointer = (@FirstKeys, @QuickKeys, @BlockKeys);

function DefEditorDialog(Dialog: Integer; Info: Pointer): Word;
begin
  DefEditorDialog := cmCancel;
end;

function CreateFindDialog: PDialog;
var
  D: PDialog;
  Control: PView;
  R: TRect;
begin
  R.Assign(0, 0, 38, 12);
  D := New(PDialog, Init(R, 'Find'));
  with D^ do
  begin
    Options := Options or ofCentered;

    R.Assign(3, 3, 32, 4);
    Control := New(PInputLine, Init(R, 80));
    Insert(Control);
    R.Assign(2, 2, 15, 3);
    Insert(New(PLabel, Init(R, '~T~ext to find', Control)));
    R.Assign(32, 3, 35, 4);
    Insert(New(PHistory, Init(R, PInputLine(Control), 10)));

    R.Assign(3, 5, 35, 7);
    Insert(New(PCheckBoxes, Init(R,
      NewSItem('~C~ase sensitive',
      NewSItem('~W~hole words only', nil)))));

    R.Assign(14, 9, 24, 11);
    Insert(New(PButton, Init(R, 'O~K~', cmOk, bfDefault)));
    Inc(R.A.X, 12); Inc(R.B.X, 12);
    Insert(New(PButton, Init(R, 'Cancel', cmCancel, bfNormal)));

    SelectNext(False);
  end;
  CreateFindDialog := D;
end;

function CreateReplaceDialog: PDialog;
var
  D: PDialog;
  Control: PView;
  R: TRect;
begin
  R.Assign(0, 0, 40, 16);
  D := New(PDialog, Init(R, 'Replace'));
  with D^ do
  begin
    Options := Options or ofCentered;

    R.Assign(3, 3, 34, 4);
    Control := New(PInputLine, Init(R, 80));
    Insert(Control);
    R.Assign(2, 2, 15, 3);
    Insert(New(PLabel, Init(R, '~T~ext to find', Control)));
    R.Assign(34, 3, 37, 4);
    Insert(New(PHistory, Init(R, PInputLine(Control), 10)));

    R.Assign(3, 6, 34, 7);
    Control := New(PInputLine, Init(R, 80));
    Insert(Control);
    R.Assign(2, 5, 12, 6);
    Insert(New(PLabel, Init(R, '~N~ew text', Control)));
    R.Assign(34, 6, 37, 7);
    Insert(New(PHistory, Init(R, PInputLine(Control), 11)));

    R.Assign(3, 8, 37, 12);
    Insert(New(PCheckBoxes, Init(R,
      NewSItem('~C~ase sensitive',
      NewSItem('~W~hole words only',
      NewSItem('~P~rompt on replace',
      NewSItem('~R~eplace all', nil)))))));

    R.Assign(17, 13, 27, 15);
    Insert(New(PButton, Init(R, 'O~K~', cmOk, bfDefault)));
    R.Assign(28, 13, 38, 15);
    Insert(New(PButton, Init(R, 'Cancel', cmCancel, bfNormal)));

    SelectNext(False);
  end;
  CreateReplaceDialog := D;
end;

function StdEditorDialog(Dialog: Integer; Info: Pointer): Word;
var
  R: TRect;
  T: TPoint;
begin
  case Dialog of
    edOutOfMemory:
      StdEditorDialog := MessageBox('Not enough memory for this operation.',
        nil, mfError + mfOkButton);
    edReadError:
      StdEditorDialog := MessageBox('Error reading file %s.',
        @Info, mfError + mfOkButton);
    edWriteError:
      StdEditorDialog := MessageBox('Error writing file %s.',
        @Info, mfError + mfOkButton);
    edCreateError:
      StdEditorDialog := MessageBox('Error creating file %s.',
        @Info, mfError + mfOkButton);
    edSaveModify:
      StdEditorDialog := MessageBox('%s has been modified. Save?',
        @Info, mfInformation + mfYesNoCancel);
    edSaveUntitled:
      StdEditorDialog := MessageBox('Save untitled file?',
        nil, mfInformation + mfYesNoCancel);
    edSaveAs:
      StdEditorDialog :=
        Application^.ExecuteDialog(New(PFileDialog, Init('*.*',
        'Save file as', '~N~ame', fdOkButton, 101)), Info);
    edFind:
      StdEditorDialog :=
        Application^.ExecuteDialog(CreateFindDialog, Info);
    edSearchFailed:
      StdEditorDialog := MessageBox('Search string not found.',
        nil, mfError + mfOkButton);
    edReplace:
      StdEditorDialog :=
        Application^.ExecuteDialog(CreateReplaceDialog, Info);
    edReplacePrompt:
      begin
        { Avoid placing the dialog on the same line as the cursor }
        R.Assign(0, 1, 40, 8);
        R.Move((Desktop^.Size.X - R.B.X) div 2, 0);
        Desktop^.MakeGlobal(R.B, T);
        Inc(T.Y);
        if TPoint(Info).Y <= T.Y then
          R.Move(0, Desktop^.Size.Y - R.B.Y - 2);
        StdEditorDialog := MessageBoxRect(R, 'Replace this occurence?',
          nil, mfYesNoCancel + mfInformation);
      end;
  end;
end;

function Min(X, Y: Integer): Integer; near; assembler;
asm
        MOV     AX,X
        CMP     AX,Y
        JLE     @@1
        MOV     AX,Y
@@1:
end;

function Max(X, Y: Integer): Integer; near; assembler;
asm
        MOV     AX,X
        CMP     AX,Y
        JGE     @@1
        MOV     AX,Y
@@1:
end;

function MinWord(X, Y: Word): Word; near; assembler;
asm
        MOV     AX,X
        CMP     AX,Y
        JBE     @@1
        MOV     AX,Y
@@1:
end;

function MaxWord(X, Y: Word): Word; near; assembler;
asm
        MOV     AX,X
        CMP     AX,Y
        JAE     @@1
        MOV     AX,Y
@@1:
end;

function CountLines(var Buf; Count: Word): Integer; near; assembler;
asm
        LES     DI,Buf
        MOV     CX,Count
        XOR     DX,DX
        MOV     AL,0DH
        CLD
@@1:    JCXZ    @@2
        REPNE   SCASB
        JNE     @@2
        INC     DX
        JMP     @@1
@@2:    MOV     AX,DX
end;

function ScanKeyMap(KeyMap: Pointer; KeyCode: Word): Word; near; assembler;
asm
        PUSH    DS
        LDS     SI,KeyMap
        MOV     DX,KeyCode
        CLD
        LODSW
        MOV     CX,AX
@@1:    LODSW
        MOV     BX,AX
        LODSW
        CMP     BL,DL
        JNE     @@3
        OR      BH,BH
        JE      @@4
        CMP     BH,DH
        JE      @@4
@@3:    LOOP    @@1
        XOR     AX,AX
@@4:    POP     DS
end;

function Scan(var Block; Size: Word; Str: String): Word; near; assembler;
asm
        PUSH    DS
        LES     DI,Block
        LDS     SI,Str
        MOV     CX,Size
        JCXZ    @@3
        CLD
        LODSB
        CMP     AL,1
        JB      @@5
        JA      @@1
        LODSB
        REPNE   SCASB
        JNE     @@3
        JMP     @@5
@@1:    XOR     AH,AH
        MOV     BX,AX
        DEC     BX
        MOV     DX,CX
        SUB     DX,AX
        JB      @@3
        LODSB
        INC     DX
        INC     DX
@@2:    DEC     DX
        MOV     CX,DX
        REPNE   SCASB
        JNE     @@3
        MOV     DX,CX
        MOV     CX,BX
        REP     CMPSB
        JE      @@4
        SUB     CX,BX
        ADD     SI,CX
        ADD     DI,CX
        INC     DI
        OR      DX,DX
        JNE     @@2
@@3:    XOR     AX,AX
        JMP     @@6
@@4:    SUB     DI,BX
@@5:    MOV     AX,DI
        SUB     AX,WORD PTR Block
@@6:    DEC     AX
        POP     DS
end;

function IScan(var Block; Size: Word; Str: String): Word; near; assembler;
var
  S: String;
asm
        PUSH    DS
        MOV     AX,SS
        MOV     ES,AX
        LEA     DI,S
        LDS     SI,Str
        XOR     AH,AH
        LODSB
        STOSB
        MOV     CX,AX
        MOV     BX,AX
        JCXZ    @@9
@@1:    LODSB
        CMP     AL,'a'
        JB      @@2
        CMP     AL,'z'
        JA      @@2
        SUB     AL,20H
@@2:    STOSB
        LOOP    @@1
        SUB     DI,BX
        LDS     SI,Block
        MOV     CX,Size
        JCXZ    @@8
        CLD
        SUB     CX,BX
        JB      @@8
        INC     CX
@@4:    MOV     AH,ES:[DI]
        AND     AH,$DF
@@5:    LODSB
        AND     AL,$DF
        CMP     AL,AH
        LOOPNE  @@5
        JNE     @@8
        DEC     SI
        MOV     DX,CX
        MOV     CX,BX
@@6:    REPE    CMPSB
        JE      @@10
        MOV     AL,DS:[SI-1]
        CMP     AL,'a'
        JB      @@7
        CMP     AL,'z'
        JA      @@7
        SUB     AL,20H
@@7:    CMP     AL,ES:[DI-1]
        JE      @@6
        SUB     CX,BX
        ADD     SI,CX
        ADD     DI,CX
        INC     SI
        MOV     CX,DX
        OR      CX,CX
        JNE     @@4
@@8:    XOR     AX,AX
        JMP     @@11
@@9:    MOV     AX, 1
        JMP     @@11
@@10:   SUB     SI,BX
        MOV     AX,SI
        SUB     AX,WORD PTR Block
        INC     AX
@@11:   DEC     AX
        POP     DS
end;

{ TIndicator }

constructor TIndicator.Init(var Bounds: TRect);
var
  R: TRect;
begin
  inherited Init(Bounds);
  GrowMode := gfGrowLoY + gfGrowHiY;
end;

procedure TIndicator.Draw;
var
  Color: Byte;
  Frame: Char;
  L: array[0..1] of Longint;
  S: String[15];
  B: TDrawBuffer;
begin
  if State and sfDragging = 0 then
  begin
    Color := GetColor(1);
    Frame := #205;
  end else
  begin
    Color := GetColor(2);
    Frame := #196;
  end;
  MoveChar(B, Frame, Color, Size.X);
  if Modified then WordRec(B[0]).Lo := 15;
  L[0] := Location.Y + 1;
  L[1] := Location.X + 1;
  FormatStr(S, ' %d:%d ', L);
  MoveStr(B[8 - Pos(':', S)], S, Color);
  WriteBuf(0, 0, Size.X, 1, B);
end;

function TIndicator.GetPalette: PPalette;
const
  P: string[Length(CIndicator)] = CIndicator;
begin
  GetPalette := @P;
end;

procedure TIndicator.SetState(AState: Word; Enable: Boolean);
begin
  inherited SetState(AState, Enable);
  if AState = sfDragging then DrawView;
end;

procedure TIndicator.SetValue(ALocation: TPoint; AModified: Boolean);
begin
  if (Longint(Location) <> Longint(ALocation)) or
    (Modified <> AModified) then
  begin
    Location := ALocation;
    Modified := AModified;
    DrawView;
  end;
end;

{ TEditor }

constructor TEditor.Init(var Bounds: TRect;
  AHScrollBar, AVScrollBar: PScrollBar;
  AIndicator: PIndicator; ABufSize: Word);
begin
  inherited Init(Bounds);
  GrowMode := gfGrowHiX + gfGrowHiY;
  Options := Options or ofSelectable;
  EventMask := evMouseDown + evKeyDown + evCommand + evBroadcast;
  ShowCursor;
  HScrollBar := AHScrollBar;
  VScrollBar := AVScrollBar;
  Indicator := AIndicator;
  BufSize := ABufSize;
  CanUndo := True;
  InitBuffer;
  if Buffer <> nil then IsValid := True else
  begin
    EditorDialog(edOutOfMemory, nil);
    BufSize := 0;
  end;
  SetBufLen(0);
end;

constructor TEditor.Load(var S: TStream);
begin
  inherited Load(S);
  GetPeerViewPtr(S, HScrollBar);
  GetPeerViewPtr(S, VScrollBar);
  GetPeerViewPtr(S, Indicator);
  S.Read(BufSize, SizeOf(Word));
  S.Read(CanUndo, SizeOf(Boolean));
  InitBuffer;
  if Buffer <> nil then IsValid := True else
  begin
    EditorDialog(edOutOfMemory, nil);
    BufSize := 0;
  end;
  Lock;
  SetBufLen(0);
end;

destructor TEditor.Done;
begin
  DoneBuffer;
  inherited Done;
end;

function TEditor.BufChar(P: Word): Char; assembler;
asm
        LES     DI,Self
        MOV     BX,P
        CMP     BX,ES:[DI].TEditor.CurPtr
        JB      @@1
        ADD     BX,ES:[DI].TEditor.GapLen
@@1:    LES     DI,ES:[DI].TEditor.Buffer
        MOV     AL,ES:[DI+BX]
end;

function TEditor.BufPtr(P: Word): Word; assembler;
asm
        LES     DI,Self
        MOV     AX,P
        CMP     AX,ES:[DI].TEditor.CurPtr
        JB      @@1
        ADD     AX,ES:[DI].TEditor.GapLen
@@1:
end;

procedure TEditor.ChangeBounds(var Bounds: TRect);
begin
  SetBounds(Bounds);
  Delta.X := Max(0, Min(Delta.X, Limit.X - Size.X));
  Delta.Y := Max(0, Min(Delta.Y, Limit.Y - Size.Y));
  Update(ufView);
end;

function TEditor.CharPos(P, Target: Word): Integer;
var
  Pos: Integer;
begin
  Pos := 0;
  while P < Target do
  begin
    if BufChar(P) = #9 then Pos := Pos or 7;
    Inc(Pos);
    Inc(P);
  end;
  CharPos := Pos;
end;

function TEditor.CharPtr(P: Word; Target: Integer): Word;
var
  Pos: Integer;
begin
  Pos := 0;
  while (Pos < Target) and (P < BufLen) and (BufChar(P) <> #13) do
  begin
    if BufChar(P) = #9 then Pos := Pos or 7;
    Inc(Pos);
    Inc(P);
  end;
  if Pos > Target then Dec(P);
  CharPtr := P;
end;

function TEditor.ClipCopy: Boolean;
begin
  ClipCopy := False;
  if (Clipboard <> nil) and (Clipboard <> @Self) then
  begin
    ClipCopy := Clipboard^.InsertFrom(@Self);
    Selecting := False;
    Update(ufUpdate);
  end;
end;

procedure TEditor.ClipCut;
begin
  if ClipCopy then DeleteSelect;
end;

procedure TEditor.ClipPaste;
begin
  if (Clipboard <> nil) and (Clipboard <> @Self) then InsertFrom(Clipboard);
end;

procedure TEditor.ConvertEvent(var Event: TEvent);
var
  ShiftState: Byte absolute $40:$17;
  Key: Word;
begin
  if Event.What = evKeyDown then
  begin
    if (ShiftState and $03 <> 0) and
      (Event.ScanCode >= $47) and (Event.ScanCode <= $51) then
      Event.CharCode := #0;
    Key := Event.KeyCode;
    if KeyState <> 0 then
    begin
      if (Lo(Key) >= $01) and (Lo(Key) <= $1A) then Inc(Key, $40);
      if (Lo(Key) >= $61) and (Lo(Key) <= $7A) then Dec(Key, $20);
    end;
    Key := ScanKeyMap(KeyMap[KeyState], Key);
    KeyState := 0;
    if Key <> 0 then
      if Hi(Key) = $FF then
      begin
        KeyState := Lo(Key);
        ClearEvent(Event);
      end else
      begin
        Event.What := evCommand;
        Event.Command := Key;
      end;
  end;
end;

function TEditor.CursorVisible: Boolean;
begin
  CursorVisible := (CurPos.Y >= Delta.Y) and (CurPos.Y < Delta.Y + Size.Y);
end;

procedure TEditor.DeleteRange(StartPtr, EndPtr: Word; DelSelect: Boolean);
begin
  if HasSelection and DelSelect then DeleteSelect else
  begin
    SetSelect(CurPtr, EndPtr, True);
    DeleteSelect;
    SetSelect(StartPtr, CurPtr, False);
    DeleteSelect;
  end;
end;

procedure TEditor.DeleteSelect;
begin
  InsertText(nil, 0, False);
end;

procedure TEditor.DoneBuffer;
begin
  if Buffer <> nil then
  begin
    FreeMem(Buffer, BufSize);
    Buffer := nil;
  end;
end;

procedure TEditor.DoSearchReplace;
var
  I: Word;
  C: TPoint;
begin
  repeat
    I := cmCancel;
    if not Search(FindStr, EditorFlags) then
    begin
      if EditorFlags and (efReplaceAll + efDoReplace) <>
          (efReplaceAll + efDoReplace) then
        EditorDialog(edSearchFailed, nil)
    end
    else if EditorFlags and efDoReplace <> 0 then
    begin
      I := cmYes;
      if EditorFlags and efPromptOnReplace <> 0 then
      begin
        MakeGlobal(Cursor, C);
        I := EditorDialog(edReplacePrompt, Pointer(C));
      end;
      if I = cmYes then
      begin
        Lock;
        InsertText(@ReplaceStr[1], Length(ReplaceStr), False);
        TrackCursor(False);
        Unlock;
      end;
    end;
  until (I = cmCancel) or (EditorFlags and efReplaceAll = 0);
end;

procedure TEditor.DoUpdate;
begin
  if UpdateFlags <> 0 then
  begin
    SetCursor(CurPos.X - Delta.X, CurPos.Y - Delta.Y);
    if UpdateFlags and ufView <> 0 then DrawView else
      if UpdateFlags and ufLine <> 0 then
        DrawLines(CurPos.Y - Delta.Y, 1, LineStart(CurPtr));
    if HScrollBar <> nil then
      HScrollBar^.SetParams(Delta.X, 0, Limit.X - Size.X, Size.X div 2, 1);
    if VScrollBar <> nil then
      VScrollBar^.SetParams(Delta.Y, 0, Limit.Y - Size.Y, Size.Y - 1, 1);
    if Indicator <> nil then Indicator^.SetValue(CurPos, Modified);
    if State and sfActive <> 0 then UpdateCommands;
    UpdateFlags := 0;
  end;
end;

procedure TEditor.Draw;
begin
  if DrawLine <> Delta.Y then
  begin
    DrawPtr := LineMove(DrawPtr, Delta.Y - DrawLine);
    DrawLine := Delta.Y;
  end;
  DrawLines(0, Size.Y, DrawPtr);
end;

procedure TEditor.DrawLines(Y, Count: Integer; LinePtr: Word);
var
  Color: Word;
  B: array[0..MaxLineLength - 1] of Word;
begin
  Color := GetColor($0201);
  while Count > 0 do
  begin
    FormatLine(B, LinePtr, Delta.X + Size.X, Color);
    WriteBuf(0, Y, Size.X, 1, B[Delta.X]);
    LinePtr := NextLine(LinePtr);
    Inc(Y);
    Dec(Count);
  end;
end;

procedure TEditor.Find;
var
  FindRec: TFindDialogRec;
begin
  with FindRec do
  begin
    Find := FindStr;
    Options := EditorFlags;
    if EditorDialog(edFind, @FindRec) <> cmCancel then
    begin
      FindStr := Find;
      EditorFlags := Options and not efDoReplace;
      DoSearchReplace;
    end;
  end;
end;

procedure TEditor.FormatLine(var DrawBuf; LinePtr: Word;
  Width: Integer; Colors: Word); assembler;
asm
        PUSH    DS
        LDS     BX,Self
        LES     DI,DrawBuf
        MOV     SI,LinePtr
        XOR     DX,DX
        CLD
        MOV     AH,Colors.Byte[0]
        MOV     CX,DS:[BX].TEditor.SelStart
        CALL    @@10
        MOV     AH,Colors.Byte[1]
        MOV     CX,DS:[BX].TEditor.CurPtr
        CALL    @@10
        ADD     SI,DS:[BX].TEditor.GapLen
        MOV     CX,DS:[BX].TEditor.SelEnd
        ADD     CX,DS:[BX].TEditor.GapLen
        CALL    @@10
        MOV     AH,Colors.Byte[0]
        MOV     CX,DS:[BX].TEditor.BufSize
        CALL    @@10
        JMP     @@31
@@10:   SUB     CX,SI
        JA      @@11
        RETN
@@11:   LDS     BX,DS:[BX].TEditor.Buffer
        ADD     SI,BX
        MOV     BX,Width
@@12:   LODSB
        CMP     AL,' '
        JB      @@20
@@13:   STOSW
        INC     DX
@@14:   CMP     DX,BX
        JAE     @@30
        LOOP    @@12
        LDS     BX,Self
        SUB     SI,DS:[BX].TEditor.Buffer.Word[0]
        RETN
@@20:   CMP     AL,0DH
        JE      @@30
        CMP     AL,09H
        JNE     @@13
        MOV     AL,' '
@@21:   STOSW
        INC     DX
        TEST    DL,7
        JNE     @@21
        JMP     @@14
@@30:   POP     CX
@@31:   MOV     AL,' '
        MOV     CX,Width
        SUB     CX,DX
        JBE     @@32
        REP     STOSW
@@32:   POP     DS
end;

function TEditor.GetMousePtr(Mouse: TPoint): Word;
begin
  MakeLocal(Mouse, Mouse);
  Mouse.X := Max(0, Min(Mouse.X, Size.X - 1));
  Mouse.Y := Max(0, Min(Mouse.Y, Size.Y - 1));
  GetMousePtr := CharPtr(LineMove(DrawPtr, Mouse.Y + Delta.Y - DrawLine),
    Mouse.X + Delta.X);
end;

function TEditor.GetPalette: PPalette;
const
  P: String[Length(CEditor)] = CEditor;
begin
  GetPalette := @P;
end;

procedure TEditor.HandleEvent(var Event: TEvent);
var
  ShiftState: Byte absolute $40:$17;
  CenterCursor: Boolean;
  SelectMode: Byte;
  I: Integer;
  NewPtr: Word;
  D, Mouse: TPoint;

procedure CheckScrollBar(P: PScrollBar; var D: Integer);
begin
  if (Event.InfoPtr = P) and (P^.Value <> D) then
  begin
    D := P^.Value;
    Update(ufView);
  end;
end;

begin
  inherited HandleEvent(Event);
  ConvertEvent(Event);
  CenterCursor := not CursorVisible;
  SelectMode := 0;
  if Selecting or (ShiftState and $03 <> 0) then SelectMode := smExtend;
  case Event.What of
    evMouseDown:
      begin
        if Event.Double then SelectMode := SelectMode or smDouble;
        repeat
          Lock;
          if Event.What = evMouseAuto then
          begin
            MakeLocal(Event.Where, Mouse);
            D := Delta;
            if Mouse.X < 0 then Dec(D.X);
            if Mouse.X >= Size.X then Inc(D.X);
            if Mouse.Y < 0 then Dec(D.Y);
            if Mouse.Y >= Size.Y then Inc(D.Y);
            ScrollTo(D.X, D.Y);
          end;
          SetCurPtr(GetMousePtr(Event.Where), SelectMode);
          SelectMode := SelectMode or smExtend;
          Unlock;
        until not MouseEvent(Event, evMouseMove + evMouseAuto);
      end;
    evKeyDown:
      case Event.CharCode of
        #9,#32..#255:
          begin
            Lock;
            if Overwrite and not HasSelection then
              if CurPtr <> LineEnd(CurPtr) then SelEnd := NextChar(CurPtr);
            InsertText(@Event.CharCode, 1, False);
            TrackCursor(CenterCursor);
            Unlock;
          end;
      else
        Exit;
      end;
    evCommand:
      case Event.Command of
        cmFind: Find;
        cmReplace: Replace;
        cmSearchAgain: DoSearchReplace;
      else
        begin
          Lock;
          case Event.Command of
            cmCut: ClipCut;
            cmCopy: ClipCopy;
            cmPaste: ClipPaste;
            cmUndo: Undo;
            cmClear: DeleteSelect;
            cmCharLeft: SetCurPtr(PrevChar(CurPtr), SelectMode);
            cmCharRight: SetCurPtr(NextChar(CurPtr), SelectMode);
            cmWordLeft: SetCurPtr(PrevWord(CurPtr), SelectMode);
            cmWordRight: SetCurPtr(NextWord(CurPtr), SelectMode);
            cmLineStart: SetCurPtr(LineStart(CurPtr), SelectMode);
            cmLineEnd: SetCurPtr(LineEnd(CurPtr), SelectMode);
            cmLineUp: SetCurPtr(LineMove(CurPtr, -1), SelectMode);
            cmLineDown: SetCurPtr(LineMove(CurPtr, 1), SelectMode);
            cmPageUp: SetCurPtr(LineMove(CurPtr, -(Size.Y - 1)), SelectMode);
            cmPageDown: SetCurPtr(LineMove(CurPtr, Size.Y - 1), SelectMode);
            cmTextStart: SetCurPtr(0, SelectMode);
            cmTextEnd: SetCurPtr(BufLen, SelectMode);
            cmNewLine: NewLine;
            cmBackSpace: DeleteRange(PrevChar(CurPtr), CurPtr, True);
            cmDelChar: DeleteRange(CurPtr, NextChar(CurPtr), True);
            cmDelWord: DeleteRange(CurPtr, NextWord(CurPtr), False);
            cmDelStart: DeleteRange(LineStart(CurPtr), CurPtr, False);
            cmDelEnd: DeleteRange(CurPtr, LineEnd(CurPtr), False);
            cmDelLine: DeleteRange(LineStart(CurPtr), NextLine(CurPtr), False);
            cmInsMode: ToggleInsMode;
            cmStartSelect: StartSelect;
            cmHideSelect: HideSelect;
            cmIndentMode: AutoIndent := not AutoIndent;
          else
            Unlock;
            Exit;
          end;
          TrackCursor(CenterCursor);
          Unlock;
        end;
      end;
    evBroadcast:
      case Event.Command of
        cmScrollBarChanged:
          if (Event.InfoPtr = HScrollBar) or
            (Event.InfoPtr = VScrollBar) then
          begin
            CheckScrollBar(HScrollBar, Delta.X);
            CheckScrollBar(VScrollBar, Delta.Y);
          end
          else
            Exit;
      else
        Exit;
      end;
  end;
  ClearEvent(Event);
end;

function TEditor.HasSelection: Boolean;
begin
  HasSelection := SelStart <> SelEnd;
end;

procedure TEditor.HideSelect;
begin
  Selecting := False;
  SetSelect(CurPtr, CurPtr, False);
end;

procedure TEditor.InitBuffer;
begin
  Buffer := MemAlloc(BufSize);
end;

function TEditor.InsertBuffer(var P: PEditBuffer; Offset, Length: Word;
  AllowUndo, SelectText: Boolean): Boolean;
var
  SelLen, DelLen, SelLines, Lines: Word;
  NewSize: Longint;
begin
  InsertBuffer := True;
  Selecting := False;
  SelLen := SelEnd - SelStart;
  if (SelLen = 0) and (Length = 0) then Exit;
  DelLen := 0;
  if AllowUndo then
    if CurPtr = SelStart then DelLen := SelLen else
      if SelLen > InsCount then DelLen := SelLen - InsCount;
  NewSize := Longint(BufLen + DelCount - SelLen + DelLen) + Length;
  if NewSize > BufLen + DelCount then
    if (NewSize > $FFF0) or not SetBufSize(NewSize) then
    begin
      EditorDialog(edOutOfMemory, nil);
      InsertBuffer := False;
      SelEnd := SelStart;
      Exit;
    end;
  SelLines := CountLines(Buffer^[BufPtr(SelStart)], SelLen);
  if CurPtr = SelEnd then
  begin
    if AllowUndo then
    begin
      if DelLen > 0 then Move(Buffer^[SelStart],
        Buffer^[CurPtr + GapLen - DelCount - DelLen], DelLen);
      Dec(InsCount, SelLen - DelLen);
    end;
    CurPtr := SelStart;
    Dec(CurPos.Y, SelLines);
  end;
  if Delta.Y > CurPos.Y then
  begin
    Dec(Delta.Y, SelLines);
    if Delta.Y < CurPos.Y then Delta.Y := CurPos.Y;
  end;
  if Length > 0 then Move(P^[Offset], Buffer^[CurPtr], Length);
  Lines := CountLines(Buffer^[CurPtr], Length);
  Inc(CurPtr, Length);
  Inc(CurPos.Y, Lines);
  DrawLine := CurPos.Y;
  DrawPtr := LineStart(CurPtr);
  CurPos.X := CharPos(DrawPtr, CurPtr);
  if not SelectText then SelStart := CurPtr;
  SelEnd := CurPtr;
  Inc(BufLen, Length - SelLen);
  Dec(GapLen, Length - SelLen);
  if AllowUndo then
  begin
    Inc(DelCount, DelLen);
    Inc(InsCount, Length);
  end;
  Inc(Limit.Y, Lines - SelLines);
  Delta.Y := Max(0, Min(Delta.Y, Limit.Y - Size.Y));
  if not IsClipboard then Modified := True;
  SetBufSize(BufLen + DelCount);
  if (SelLines = 0) and (Lines = 0) then Update(ufLine) else Update(ufView);
end;

function TEditor.InsertFrom(Editor: PEditor): Boolean;
begin
  InsertFrom := InsertBuffer(Editor^.Buffer,
    Editor^.BufPtr(Editor^.SelStart),
    Editor^.SelEnd - Editor^.SelStart, CanUndo, IsClipboard);
end;

function TEditor.InsertText(Text: Pointer; Length: Word;
  SelectText: Boolean): Boolean;
begin
  InsertText := InsertBuffer(PEditBuffer(Text),
    0, Length, CanUndo, SelectText);
end;

function TEditor.IsClipboard: Boolean;
begin
  IsClipboard := Clipboard = @Self;
end;

function TEditor.LineEnd(P: Word): Word; assembler;
asm
        PUSH    DS
        LDS     SI,Self
        LES     BX,DS:[SI].TEditor.Buffer
        MOV     DI,P
        MOV     AL,0DH
        CLD
        MOV     CX,DS:[SI].TEditor.CurPtr
        SUB     CX,DI
        JBE     @@1
        ADD     DI,BX
        REPNE   SCASB
        JE      @@2
        MOV     DI,DS:[SI].TEditor.CurPtr
@@1:    MOV     CX,DS:[SI].TEditor.BufLen
        SUB     CX,DI
        JCXZ    @@4
        ADD     BX,DS:[SI].TEditor.GapLen
        ADD     DI,BX
        REPNE   SCASB
        JNE     @@3
@@2:    DEC     DI
@@3:    SUB     DI,BX
@@4:    MOV     AX,DI
        POP     DS
end;

function TEditor.LineMove(P: Word; Count: Integer): Word;
var
  Pos: Integer;
  I: Word;
begin
  I := P;
  P := LineStart(P);
  Pos := CharPos(P, I);
  while Count <> 0 do
  begin
    I := P;
    if Count < 0 then
    begin
      P := PrevLine(P);
      Inc(Count);
    end else
    begin
      P := NextLine(P);
      Dec(Count);
    end;
  end;
  if P <> I then P := CharPtr(P, Pos);
  LineMove := P;
end;

function TEditor.LineStart(P: Word): Word; assembler;
asm
        PUSH    DS
        LDS     SI,Self
        LES     BX,DS:[SI].TEditor.Buffer
        MOV     DI,P
        MOV     AL,0DH
        STD
        MOV     CX,DI
        SUB     CX,DS:[SI].TEditor.CurPtr
        JBE     @@1
        ADD     BX,DS:[SI].TEditor.GapLen
        ADD     DI,BX
        DEC     DI
        REPNE   SCASB
        JE      @@2
        SUB     BX,DS:[SI].TEditor.GapLen
        MOV     DI,DS:[SI].TEditor.CurPtr
@@1:    MOV     CX,DI
        JCXZ    @@4
        ADD     DI,BX
        DEC     DI
        REPNE   SCASB
        JNE     @@3
@@2:    INC     DI
        INC     DI
        SUB     DI,BX
        CMP     DI,DS:[SI].TEditor.CurPtr
        JE      @@4
        CMP     DI,DS:[SI].TEditor.BufLen
        JE      @@4
        CMP     ES:[BX+DI].Byte,0AH
        JNE     @@4
        INC     DI
        JMP     @@4
@@3:    XOR     DI,DI
@@4:    MOV     AX,DI
        POP     DS
end;

procedure TEditor.Lock;
begin
  Inc(LockCount);
end;

procedure TEditor.NewLine;
const
  CrLf: array[1..2] of Char = #13#10;
var
  I, P: Word;
begin
  P := LineStart(CurPtr);
  I := P;
  while (I < CurPtr) and ((Buffer^[I] = ' ') or (Buffer^[I] = #9)) do Inc(I);
  InsertText(@CrLf, 2, False);
  if AutoIndent then InsertText(@Buffer^[P], I - P, False);
end;

function TEditor.NextChar(P: Word): Word; assembler;
asm
        PUSH    DS
        LDS     SI,Self
        MOV     DI,P
        CMP     DI,DS:[SI].TEditor.BufLen
        JE      @@2
        INC     DI
        CMP     DI,DS:[SI].TEditor.BufLen
        JE      @@2
        LES     BX,DS:[SI].TEditor.Buffer
        CMP     DI,DS:[SI].TEditor.CurPtr
        JB      @@1
        ADD     BX,DS:[SI].TEditor.GapLen
@@1:    CMP     ES:[BX+DI-1].Word,0A0DH
        JNE     @@2
        INC     DI
@@2:    MOV     AX,DI
        POP     DS
end;

function TEditor.NextLine(P: Word): Word;
begin
  NextLine := NextChar(LineEnd(P));
end;

function TEditor.NextWord(P: Word): Word;
begin
  while (P < BufLen) and (BufChar(P) in WordChars) do
    P := NextChar(P);
  while (P < BufLen) and not (BufChar(P) in WordChars) do
    P := NextChar(P);
  NextWord := P;
end;

function TEditor.PrevChar(P: Word): Word; assembler;
asm
        PUSH    DS
        LDS     SI,Self
        MOV     DI,P
        OR      DI,DI
        JE      @@2
        DEC     DI
        JE      @@2
        LES     BX,DS:[SI].TEditor.Buffer
        CMP     DI,DS:[SI].TEditor.CurPtr
        JB      @@1
        ADD     BX,DS:[SI].TEditor.GapLen
@@1:    CMP     ES:[BX+DI-1].Word,0A0DH
        JNE     @@2
        DEC     DI
@@2:    MOV     AX,DI
        POP     DS
end;

function TEditor.PrevLine(P: Word): Word;
begin
  PrevLine := LineStart(PrevChar(P));
end;

function TEditor.PrevWord(P: Word): Word;
begin
  while (P > 0) and not (BufChar(PrevChar(P)) in WordChars) do
    P := PrevChar(P);
  while (P > 0) and (BufChar(PrevChar(P)) in WordChars) do
    P := PrevChar(P);
  PrevWord := P;
end;

procedure TEditor.Replace;
var
  ReplaceRec: TReplaceDialogRec;
begin
  with ReplaceRec do
  begin
    Find := FindStr;
    Replace := ReplaceStr;
    Options := EditorFlags;
    if EditorDialog(edReplace, @ReplaceRec) <> cmCancel then
    begin
      FindStr := Find;
      ReplaceStr := Replace;
      EditorFlags := Options or efDoReplace;
      DoSearchReplace;
    end;
  end;
end;

procedure TEditor.ScrollTo(X, Y: Integer);
begin
  X := Max(0, Min(X, Limit.X - Size.X));
  Y := Max(0, Min(Y, Limit.Y - Size.Y));
  if (X <> Delta.X) or (Y <> Delta.Y) then
  begin
    Delta.X := X;
    Delta.Y := Y;
    Update(ufView);
  end;
end;

function TEditor.Search(const FindStr: String; Opts: Word): Boolean;
var
  I, Pos: Word;
begin
  Search := False;
  Pos := CurPtr;
  repeat
    if Opts and efCaseSensitive <> 0 then
      I := Scan(Buffer^[BufPtr(Pos)], BufLen - Pos, FindStr)
    else I := IScan(Buffer^[BufPtr(Pos)], BufLen - Pos, FindStr);
    if (I <> sfSearchFailed) then
    begin
      Inc(I, Pos);
      if (Opts and efWholeWordsOnly = 0) or
         not (((I <> 0) and (BufChar(I - 1) in WordChars)) or
              ((I + Length(FindStr) <> BufLen) and
               (BufChar(I + Length(FindStr)) in WordChars))) then
      begin
        Lock;
        SetSelect(I, I + Length(FindStr), False);
        TrackCursor(not CursorVisible);
        Unlock;
        Search := True;
        Exit;
      end else Pos := I + 1;
    end;
  until I = sfSearchFailed;
end;

procedure TEditor.SetBufLen(Length: Word);
begin
  BufLen := Length;
  GapLen := BufSize - Length;
  SelStart := 0;
  SelEnd := 0;
  CurPtr := 0;
  Longint(CurPos) := 0;
  Longint(Delta) := 0;
  Limit.X := MaxLineLength;
  Limit.Y := CountLines(Buffer^[GapLen], BufLen) + 1;
  DrawLine := 0;
  DrawPtr := 0;
  DelCount := 0;
  InsCount := 0;
  Modified := False;
  Update(ufView);
end;

function TEditor.SetBufSize(NewSize: Word): Boolean;
begin
  SetBufSize := NewSize <= BufSize;
end;

procedure TEditor.SetCmdState(Command: Word; Enable: Boolean);
var
  S: TCommandSet;
begin
  S := [Command];
  if Enable and (State and sfActive <> 0) then
    EnableCommands(S) else DisableCommands(S);
end;

procedure TEditor.SetCurPtr(P: Word; SelectMode: Byte);
var
  Anchor: Word;
begin
  if SelectMode and smExtend = 0 then Anchor := P else
    if CurPtr = SelStart then Anchor := SelEnd else Anchor := SelStart;
  if P < Anchor then
  begin
    if SelectMode and smDouble <> 0 then
    begin
      P := PrevLine(NextLine(P));
      Anchor := NextLine(PrevLine(Anchor));
    end;
    SetSelect(P, Anchor, True);
  end else
  begin
    if SelectMode and smDouble <> 0 then
    begin
      P := NextLine(P);
      Anchor := PrevLine(NextLine(Anchor));
    end;
    SetSelect(Anchor, P, False);
  end;
end;

procedure TEditor.SetSelect(NewStart, NewEnd: Word; CurStart: Boolean);
var
  Flags: Byte;
  P, L: Word;
begin
  if CurStart then P := NewStart else P := NewEnd;
  Flags := ufUpdate;
  if (NewStart <> SelStart) or (NewEnd <> SelEnd) then
    if (NewStart <> NewEnd) or (SelStart <> SelEnd) then
      Flags := ufView;
  if P <> CurPtr then
  begin
    if P > CurPtr then
    begin
      L := P - CurPtr;
      Move(Buffer^[CurPtr + GapLen], Buffer^[CurPtr], L);
      Inc(CurPos.Y, CountLines(Buffer^[CurPtr], L));
      CurPtr := P;
    end else
    begin
      L := CurPtr - P;
      CurPtr := P;
      Dec(CurPos.Y, CountLines(Buffer^[CurPtr], L));
      Move(Buffer^[CurPtr], Buffer^[CurPtr + GapLen], L);
    end;
    DrawLine := CurPos.Y;
    DrawPtr := LineStart(P);
    CurPos.X := CharPos(DrawPtr, P);
    DelCount := 0;
    InsCount := 0;
    SetBufSize(BufLen);
  end;
  SelStart := NewStart;
  SelEnd := NewEnd;
  Update(Flags);
end;

procedure TEditor.SetState(AState: Word; Enable: Boolean);
begin
  inherited SetState(AState, Enable);
  case AState of
    sfActive:
      begin
        if HScrollBar <> nil then HScrollBar^.SetState(sfVisible, Enable);
        if VScrollBar <> nil then VScrollBar^.SetState(sfVisible, Enable);
        if Indicator <> nil then Indicator^.SetState(sfVisible, Enable);
        UpdateCommands;
      end;
    sfExposed:
      if Enable then Unlock;
  end;
end;

procedure TEditor.StartSelect;
begin
  HideSelect;
  Selecting := True;
end;

procedure TEditor.Store(var S: TStream);
begin
  inherited Store(S);
  PutPeerViewPtr(S, HScrollBar);
  PutPeerViewPtr(S, VScrollBar);
  PutPeerViewPtr(S, Indicator);
  S.Write(BufSize, SizeOf(Word));
  S.Write(CanUndo, SizeOf(Boolean));
end;

procedure TEditor.ToggleInsMode;
begin
  Overwrite := not Overwrite;
  SetState(sfCursorIns, not GetState(sfCursorIns));
end;

procedure TEditor.TrackCursor(Center: Boolean);
begin
  if Center then
    ScrollTo(CurPos.X - Size.X + 1, CurPos.Y - Size.Y div 2) else
    ScrollTo(Max(CurPos.X - Size.X + 1, Min(Delta.X, CurPos.X)),
      Max(CurPos.Y - Size.Y + 1, Min(Delta.Y, CurPos.Y)));
end;

procedure TEditor.Undo;
var
  Length: Word;
begin
  if (DelCount <> 0) or (InsCount <> 0) then
  begin
    SelStart := CurPtr - InsCount;
    SelEnd := CurPtr;
    Length := DelCount;
    DelCount := 0;
    InsCount := 0;
    InsertBuffer(Buffer, CurPtr + GapLen - Length, Length, False, True);
  end;
end;

procedure TEditor.Unlock;
begin
  if LockCount > 0 then
  begin
    Dec(LockCount);
    if LockCount = 0 then DoUpdate;
  end;
end;

procedure TEditor.Update(AFlags: Byte);
begin
  UpdateFlags := UpdateFlags or AFlags;
  if LockCount = 0 then DoUpdate;
end;

procedure TEditor.UpdateCommands;
begin
  SetCmdState(cmUndo, (DelCount <> 0) or (InsCount <> 0));
  if not IsClipboard then
  begin
    SetCmdState(cmCut, HasSelection);
    SetCmdState(cmCopy, HasSelection);
    SetCmdState(cmPaste, (Clipboard <> nil) and (Clipboard^.HasSelection));
  end;
  SetCmdState(cmClear, HasSelection);
  SetCmdState(cmFind, True);
  SetCmdState(cmReplace, True);
  SetCmdState(cmSearchAgain, True);
end;

function TEditor.Valid(Command: Word): Boolean;
begin
  Valid := IsValid;
end;

{ TMemo }

constructor TMemo.Load(var S: TStream);
var
  Length: Word;
begin
  inherited Load(S);
  S.Read(Length, SizeOf(Word));
  if IsValid then
  begin
    S.Read(Buffer^[BufSize - Length], Length);
    SetBufLen(Length);
  end
  else S.Seek(S.GetPos + Length);
end;

function TMemo.DataSize: Word;
begin
  DataSize := BufSize + SizeOf(Word);
end;

procedure TMemo.GetData(var Rec);
var
  Data: TMemoData absolute Rec;
begin
  Data.Length := BufLen;
  Move(Buffer^, Data.Buffer, CurPtr);
  Move(Buffer^[CurPtr + GapLen], Data.Buffer[CurPtr], BufLen - CurPtr);
  FillChar(Data.Buffer[BufLen], BufSize - BufLen, 0);
end;

function TMemo.GetPalette: PPalette;
const
  P: String[Length(CMemo)] = CMemo;
begin
  GetPalette := @P;
end;

procedure TMemo.HandleEvent(var Event: TEvent);
begin
  if (Event.What <> evKeyDown) or (Event.KeyCode <> kbTab) then
    inherited HandleEvent(Event);
end;

procedure TMemo.SetData(var Rec);
var
  Data: TMemoData absolute Rec;
begin
  Move(Data.Buffer, Buffer^[BufSize - Data.Length], Data.Length);
  SetBufLen(Data.Length);
end;

procedure TMemo.Store(var S: TStream);
begin
  inherited Store(S);
  S.Write(BufLen, SizeOf(Word));
  S.Write(Buffer^, CurPtr);
  S.Write(Buffer^[CurPtr + GapLen], BufLen - CurPtr);
end;

{ TFileEditor }

constructor TFileEditor.Init(var Bounds: TRect;
  AHScrollBar, AVScrollBar: PScrollBar;
  AIndicator: PIndicator; AFileName: FNameStr);
begin
  inherited Init(Bounds, AHScrollBar, AVScrollBar, AIndicator, 0);
  if AFileName <> '' then
  begin
    FileName := FExpand(AFileName);
    if IsValid then IsValid := LoadFile;
  end;
end;

constructor TFileEditor.Load(var S: TStream);
var
  SStart, SEnd, Curs: Word;
begin
  inherited Load(S);
  BufSize := 0;
  S.Read(FileName[0], SizeOf(Char));
  S.Read(Filename[1], Length(FileName));
  if IsValid then IsValid := LoadFile;
  S.Read(SStart, SizeOf(Word));
  S.Read(SEnd, SizeOf(Word));
  S.Read(Curs, SizeOf(Word));
  if IsValid and (SEnd <= BufLen) then
  begin
    SetSelect(SStart, SEnd, Curs = SStart);
    TrackCursor(True);
  end;
end;

procedure TFileEditor.DoneBuffer;
begin
  if Buffer <> nil then DisposeBuffer(Buffer);
end;

procedure TFileEditor.HandleEvent(var Event: TEvent);
begin
  inherited HandleEvent(Event);
  case Event.What of
    evCommand:
      case Event.Command of
        cmSave: Save;
        cmSaveAs: SaveAs;
      else
        Exit;
      end;
  else
    Exit;
  end;
  ClearEvent(Event);
end;

procedure TFileEditor.InitBuffer;
begin
  NewBuffer(Pointer(Buffer), $1000);
end;

function TFileEditor.LoadFile: Boolean;
var
  Length: Word;
  FSize: Longint;
  F: File;
begin
  LoadFile := False;
  Length := 0;
  Assign(F, FileName);
  Reset(F, 1);
  if IOResult <> 0 then EditorDialog(edReadError, @FileName)
  else
  begin
    FSize := FileSize(F);
    if (FSize > $FFF0) or not SetBufSize(Word(FSize)) then
      EditorDialog(edOutOfMemory, nil) else
    begin
      BlockRead(F, Buffer^[BufSize - Word(FSize)], FSize);
      if IOResult <> 0 then EditorDialog(edReadError, @FileName) else
      begin
        LoadFile := True;
        Length := FSize;
      end;
    end;
    Close(F);
  end;
  SetBufLen(Length);
end;

function TFileEditor.Save: Boolean;
begin
  if FileName = '' then Save := SaveAs else Save := SaveFile;
end;

function TFileEditor.SaveAs: Boolean;
begin
  SaveAs := False;
  if EditorDialog(edSaveAs, @FileName) <> cmCancel then
  begin
    FileName := FExpand(FileName);
    Message(Owner, evBroadcast, cmUpdateTitle, nil);
    SaveAs := SaveFile;
    if IsClipboard then FileName := '';
  end;
end;

function TFileEditor.SaveFile: Boolean;
var
  F: File;
  BackupName: FNameStr;
  D: DirStr;
  N: NameStr;
  E: ExtStr;
begin
  SaveFile := False;
  if EditorFlags and efBackupFiles <> 0 then
  begin
    FSplit(FileName, D, N, E);
    BackupName := D + N + '.BAK';
    Assign(F, BackupName);
    Erase(F);
    Assign(F, FileName);
    Rename(F, BackupName);
    InOutRes := 0;
  end;
  Assign(F, FileName);
  Rewrite(F, 1);
  if IOResult <> 0 then EditorDialog(edCreateError, @FileName) else
  begin
    BlockWrite(F, Buffer^, CurPtr);
    BlockWrite(F, Buffer^[CurPtr + GapLen], BufLen - CurPtr);
    if IOResult <> 0 then EditorDialog(edWriteError, @FileName) else
    begin
      Modified := False;
      Update(ufUpdate);
      SaveFile := True;
    end;
    Close(F);
  end;
end;

function TFileEditor.SetBufSize(NewSize: Word): Boolean;
var
  N: Word;
begin
  SetBufSize := False;
  if NewSize = 0 then NewSize := $1000 else
    if NewSize > $F000 then NewSize := $FFF0 else
      NewSize := (NewSize + $0FFF) and $F000;
  if NewSize <> BufSize then
  begin
    if NewSize > BufSize then
      if not SetBufferSize(Buffer, NewSize) then Exit;
    N := BufLen - CurPtr + DelCount;
    Move(Buffer^[BufSize - N], Buffer^[NewSize - N], N);
    if NewSize < BufSize then SetBufferSize(Buffer, NewSize);
    BufSize := NewSize;
    GapLen := BufSize - BufLen;
  end;
  SetBufSize := True;
end;

procedure TFileEditor.Store(var S: TStream);
begin
  inherited Store(S);
  S.Write(FileName, Length(FileName) + 1);
  S.Write(SelStart, SizeOf(Word) * 3);
end;

procedure TFileEditor.UpdateCommands;
begin
  inherited UpdateCommands;
  SetCmdState(cmSave, True);
  SetCmdState(cmSaveAs, True);
end;

function TFileEditor.Valid(Command: Word): Boolean;
var
  D: Integer;
begin
  if Command = cmValid then Valid := IsValid else
  begin
    Valid := True;
    if Modified then
    begin
      if FileName = '' then D := edSaveUntitled else D := edSaveModify;
      case EditorDialog(D, @FileName) of
        cmYes: Valid := Save;
        cmNo: Modified := False;
        cmCancel: Valid := False;
      end;
    end;
  end;
end;

{ TEditWindow }

constructor TEditWindow.Init(var Bounds: TRect;
  FileName: FNameStr; ANumber: Integer);
var
  HScrollBar, VScrollBar: PScrollBar;
  Indicator: PIndicator;
  R: TRect;
begin
  inherited Init(Bounds, '', ANumber);
  Options := Options or ofTileable;
  R.Assign(18, Size.Y - 1, Size.X - 2, Size.Y);
  HScrollBar := New(PScrollBar, Init(R));
  HScrollBar^.Hide;
  Insert(HScrollBar);
  R.Assign(Size.X - 1, 1, Size.X, Size.Y - 1);
  VScrollBar := New(PScrollBar, Init(R));
  VScrollBar^.Hide;
  Insert(VScrollBar);
  R.Assign(2, Size.Y - 1, 16, Size.Y);
  Indicator := New(PIndicator, Init(R));
  Indicator^.Hide;
  Insert(Indicator);
  GetExtent(R);
  R.Grow(-1, -1);
  Editor := New(PFileEditor, Init(
    R, HScrollBar, VScrollBar, Indicator, FileName));
  Insert(Editor);
end;

constructor TEditWindow.Load(var S: TStream);
begin
  inherited Load(S);
  GetSubViewPtr(S, Editor);
end;

procedure TEditWindow.Close;
begin
  if Editor^.IsClipboard then Hide else inherited Close;
end;

function TEditWindow.GetTitle(MaxSize: Integer): TTitleStr;
begin
  if Editor^.IsClipboard then GetTitle := 'Clipboard' else
    if Editor^.FileName = '' then GetTitle := 'Untitled' else
      GetTitle := Editor^.FileName;
end;

procedure TEditWindow.HandleEvent(var Event: TEvent);
begin
  inherited HandleEvent(Event);
  if (Event.What = evBroadcast) and (Event.Command = cmUpdateTitle) then
  begin
    Frame^.DrawView;
    ClearEvent(Event);
  end;
end;

procedure TEditWindow.SizeLimits(var Min, Max: TPoint);
begin
  inherited SizeLimits(Min, Max);
  Min.X := 23;
end;

procedure TEditWindow.Store(var S: TStream);
begin
  inherited Store(S);
  PutSubViewPtr(S, Editor);
end;

procedure RegisterEditors;
begin
  RegisterType(REditor);
  RegisterType(RMemo);
  RegisterType(RFileEditor);
  RegisterType(RIndicator);
  RegisterType(REditWindow);
end;

end.
